package com.gitee.loyo

import com.gitee.loyo.act.Column
import com.gitee.loyo.act.Model
import com.gitee.loyo.act.Record
import com.gitee.loyo.interfaces.ParameterizeSQL
import com.gitee.loyo.interfaces.impl.BaseModelSQL
import java.lang.UnsupportedOperationException
import java.lang.reflect.*
import java.util.concurrent.atomic.AtomicInteger
import kotlin.reflect.KClass
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.jvm.kotlinFunction

object Helper {

    fun typeToClass(type: Type, pos: Int): Class<*>{
        when (type) {
            is TypeVariable<*> -> {
                return type.bounds[pos] as Class<*>
            }
            is WildcardType -> {
                return (type.lowerBounds.getOrNull(pos)?:type.upperBounds.getOrNull(pos)!!) as Class<*>
            }
            is ParameterizedType -> {
                return type.rawType as Class<*>
            }
            is Class<*> -> {
                return type
            }

        }
        throw UnsupportedOperationException("无法处理的类型$type")
    }

    /**
     * 使用模板生成sql
     */
    fun template(sql: String, args: Map<String, Any?>): ParameterizeSQL {
        val parameters = ArrayList<Any?>()
        val sqlBuilder = StringBuilder()
        var start = 0
        var end = sql.indexOf(':', start)
        while (end >= 0){
            if(end > 0 && sql[end - 1] == '\\'){
                sqlBuilder.append(sql, start, end - 1).append(":")
                end++
            }else{
                sqlBuilder.append(sql, start, end)

                val (key, members, index) = com.gitee.loyo.Helper.getKeyWithMembers(sql, end + 1)
                if(key.isEmpty())
                    throw IllegalArgumentException("单独使用冒号需要使用\\转义")
                end = index
                sqlBuilder.append("?")
                val value: Any? = args[key]?.let { obj ->
                    if(members == null){
                        obj
                    }else{
                        com.gitee.loyo.KSQL.reflection.getFieldValue(obj, *(members!!.toTypedArray()))
                    }
                }
                parameters.add(value)
            }

            start = end
            end = sql.indexOf(':', start)
        }
        if(start < sql.length){
            sqlBuilder.append(sql, start, sql.length)
        }

        return object : ParameterizeSQL {
            override fun getSQL(): String {
                return sqlBuilder.toString()
            }

            override fun getParameters(): Array<Any?> {
                return parameters.toArray()
            }

        }
    }

    private fun getKeyWithMembers(sql: String, start: Int): Triple<String, ArrayList<String>?, Int>{
        var key = ""
        var members:ArrayList<String>? = null
        var dotIndex = 0
        for(i in start until sql.length){
            val c = sql[i]
            when{
                c == '.' -> {
                    if(key.isEmpty()){
                        key = sql.substring(start, i)
                    }else{
                        members = members?:ArrayList()
                        members.add(sql.substring(dotIndex, i))
                    }
                    dotIndex = i + 1
                }
                c == '_' -> {}
                (c in 'a'..'z') || (c in 'A'..'Z') || (c in '0'..'9') -> {}
                else -> {
                    if(key.isEmpty()){
                        key = sql.substring(start, i)
                    }else{
                        members = members?:ArrayList()
                        members.add(sql.substring(dotIndex, i))
                    }
                    return Triple(key, members, i)
                }
            }
        }
        if(key.isEmpty()){
            key = sql.substring(start, sql.length)
        }
        return Triple(key, members, sql.length)
    }

    inline fun <reified T: Any> mapper(): T{
        return com.gitee.loyo.Helper.mapper(T::class.java)
    }

    fun <T: Any> mapper(type: Class<T>): T{
        return Proxy.newProxyInstance(
            com.gitee.loyo.KSQL::class.java.classLoader,
            arrayOf<Class<*>>(type)
        ) { _, method, args ->
            val argsMap = args?.let {
                val kParameters = method.kotlinFunction!!.parameters
                kParameters.drop(kParameters.size - args.size).mapIndexed { index, parameter ->
                    (parameter.name?.ifEmpty{ null }?:"arg${index}") to args[index]
                }.toMap()
            }?: emptyMap()
            com.gitee.loyo.KSQL.tryGetConnection().invokeMapperFunction(method, argsMap)
        } as T
    }

    fun <T: Model<*, *>> createModel(modelKClass: KClass<T>, pattern: String = ""): T{
        val counter = AtomicInteger()
        val construct = modelKClass.primaryConstructor!!
        val args = construct.parameters.mapIndexed{ idx, it ->
            val columnKClass: KClass<out Column<*>> = it.type.classifier as KClass<out Column<*>>
            val property = columnKClass.primaryConstructor!!.call(it.name!!, counter)
            it.annotations.forEach { annotation ->
                when(annotation){
                    is com.gitee.loyo.PrimaryKey -> {
                        property.primaryKey = annotation
                    }
                    is com.gitee.loyo.NotNull -> property.notNull = annotation
                    is com.gitee.loyo.Length -> property.length = annotation
                    is com.gitee.loyo.Default -> property.default = annotation
                }
            }
            property
        }
        val model = construct.call(*(args.toTypedArray()))
        model.pattern = pattern
        args.forEach {
            it.model = model
        }
        return model
    }

    /**
     * 批量插入Record
     */
    fun <T: Record<*, T>> insertModelBatch(collection: Collection<T>, tableName: String? = null){
        if(collection.isEmpty())
            return

        val first = collection.first()
        val tableName = tableName?:first.getTableName()
        val columnNames = arrayListOf<String>()
        val parametersList = arrayListOf<List<Any?>>()
        val isAutoIncrementID = first.autoIncrementID()
        for (record in collection) {
            val parameters = arrayListOf<Any?>()
            BaseModelSQL.eachFields(record){ columnName, value ->
                if(isAutoIncrementID && value == null){
                    return@eachFields
                }
                if(parametersList.isEmpty()) {
                    columnNames.add(columnName)
                }
                parameters.add(value)
            }
            parametersList.add(parameters)
        }

        com.gitee.loyo.KSQL.tryGetConnection().batch(
            "insert into $tableName (${columnNames.joinToString(", ")}) values (${columnNames.joinToString(", "){ "?" }})",
            parametersList
        )
    }

    /**
     * 批量更新Record
     */
    fun <T: Record<*, T>> updateModelBatch(collection: Collection<T>, tableName: String? = null){
        if(collection.isEmpty())
            return

        val first = collection.first()
        val tableName = tableName?:first.getTableName()
        val primaryKeyName = first.getPrimaryKeyName()
        val columnNames = arrayListOf<String>()
        val parametersList = arrayListOf<List<Any?>>()
        for (record in collection) {
            val parameters = arrayListOf<Any?>()
            var primaryKeyValue: Any? = null
            BaseModelSQL.eachFields(record){ columnName, value ->
                if(primaryKeyName == columnName){
                    primaryKeyValue = value
                    return@eachFields
                }
                if(parametersList.isEmpty()) {
                    columnNames.add(columnName)
                }
                parameters.add(value)
            }
            if(primaryKeyValue == null)
                throw IllegalArgumentException("未找到ID: $record")
            parameters.add(primaryKeyValue)
            parametersList.add(parameters)
        }

        com.gitee.loyo.KSQL.tryGetConnection().batch(
            "update $tableName set ${columnNames.joinToString(", ") { "$it = ?" }} where $primaryKeyName = ?",
            parametersList
        )
    }

    /**
     * 批量删除Record
     */
    fun <T: Record<*, T>> deleteModelBatch(collection: Collection<T>, tableName: String? = null){
        if(collection.isEmpty())
            return

        val first = collection.first()
        val tableName = tableName?:first.getTableName()
        val primaryKeyName = first.getPrimaryKeyName()
        val parametersList = arrayListOf<List<Any?>>()
        for (record in collection) {
            val parameters = arrayListOf<Any?>()
            var primaryKeyValue: Any? = null
            BaseModelSQL.eachFields(record){ columnName, value ->
                if(primaryKeyName == columnName){
                    primaryKeyValue = value
                    return@eachFields
                }
            }
            if(primaryKeyValue == null)
                throw IllegalArgumentException("未找到ID: $record")
            parameters.add(primaryKeyValue)
            parametersList.add(parameters)
        }

        com.gitee.loyo.KSQL.tryGetConnection().batch(
            "delete from $tableName where $primaryKeyName = ?",
            parametersList
        )
    }

    /**
     * 创建method各参数值的map
     */
    fun buildArgsMap(obj: Any?, method: Method, args: List<Any?>?): LinkedHashMap<String, Any?>{
        val argsMap: LinkedHashMap<String, Any?> = obj?.let{ com.gitee.loyo.KSQL.reflection.toMap(it){
            com.gitee.loyo.Utils.isNotObject(
                it.type.kotlin
            )
        } }?: linkedMapOf()
        args?.also {
            val kParameters = method.kotlinFunction!!.parameters
            kParameters.drop(kParameters.size - args.size).mapIndexed { index, parameter ->
                argsMap[(parameter.name?.ifEmpty{ null }?:"arg${index}")] = args[index]
            }
        }
        return argsMap
    }
}